/*!
 * # Semantic UI 2.3.0 - State
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ($, window, document, undefined) {

    "use strict";

    window = (typeof window != 'undefined' && window.Math == Math)
        ? window
        : (typeof self != 'undefined' && self.Math == Math)
            ? self
            : Function('return this')()
    ;

    $.fn.state = function (parameters) {
        var
            $allModules = $(this),

            moduleSelector = $allModules.selector || '',

            hasTouch = ('ontouchstart' in document.documentElement),
            time = new Date().getTime(),
            performance = [],

            query = arguments[0],
            methodInvoked = (typeof query == 'string'),
            queryArguments = [].slice.call(arguments, 1),

            returnedValue
        ;
        $allModules
            .each(function () {
                var
                    settings = ($.isPlainObject(parameters))
                        ? $.extend(true, {}, $.fn.state.settings, parameters)
                        : $.extend({}, $.fn.state.settings),

                    error = settings.error,
                    metadata = settings.metadata,
                    className = settings.className,
                    namespace = settings.namespace,
                    states = settings.states,
                    text = settings.text,

                    eventNamespace = '.' + namespace,
                    moduleNamespace = namespace + '-module',

                    $module = $(this),

                    element = this,
                    instance = $module.data(moduleNamespace),

                    module
                ;
                module = {

                    initialize: function () {
                        module.verbose('Initializing module');

                        // allow module to guess desired state based on element
                        if (settings.automatic) {
                            module.add.defaults();
                        }

                        // bind events with delegated events
                        if (settings.context && moduleSelector !== '') {
                            $(settings.context)
                                .on(moduleSelector, 'mouseenter' + eventNamespace, module.change.text)
                                .on(moduleSelector, 'mouseleave' + eventNamespace, module.reset.text)
                                .on(moduleSelector, 'click' + eventNamespace, module.toggle.state)
                            ;
                        }
                        else {
                            $module
                                .on('mouseenter' + eventNamespace, module.change.text)
                                .on('mouseleave' + eventNamespace, module.reset.text)
                                .on('click' + eventNamespace, module.toggle.state)
                            ;
                        }
                        module.instantiate();
                    },

                    instantiate: function () {
                        module.verbose('Storing instance of module', module);
                        instance = module;
                        $module
                            .data(moduleNamespace, module)
                        ;
                    },

                    destroy: function () {
                        module.verbose('Destroying previous module', instance);
                        $module
                            .off(eventNamespace)
                            .removeData(moduleNamespace)
                        ;
                    },

                    refresh: function () {
                        module.verbose('Refreshing selector cache');
                        $module = $(element);
                    },

                    add: {
                        defaults: function () {
                            var
                                userStates = parameters && $.isPlainObject(parameters.states)
                                    ? parameters.states
                                    : {}
                            ;
                            $.each(settings.defaults, function (type, typeStates) {
                                if (module.is[type] !== undefined && module.is[type]()) {
                                    module.verbose('Adding default states', type, element);
                                    $.extend(settings.states, typeStates, userStates);
                                }
                            });
                        }
                    },

                    is: {

                        active: function () {
                            return $module.hasClass(className.active);
                        },
                        loading: function () {
                            return $module.hasClass(className.loading);
                        },
                        inactive: function () {
                            return !($module.hasClass(className.active));
                        },
                        state: function (state) {
                            if (className[state] === undefined) {
                                return false;
                            }
                            return $module.hasClass(className[state]);
                        },

                        enabled: function () {
                            return !($module.is(settings.filter.active));
                        },
                        disabled: function () {
                            return ($module.is(settings.filter.active));
                        },
                        textEnabled: function () {
                            return !($module.is(settings.filter.text));
                        },

                        // definitions for automatic type detection
                        button: function () {
                            return $module.is('.button:not(a, .submit)');
                        },
                        input: function () {
                            return $module.is('input');
                        },
                        progress: function () {
                            return $module.is('.ui.progress');
                        }
                    },

                    allow: function (state) {
                        module.debug('Now allowing state', state);
                        states[state] = true;
                    },
                    disallow: function (state) {
                        module.debug('No longer allowing', state);
                        states[state] = false;
                    },

                    allows: function (state) {
                        return states[state] || false;
                    },

                    enable: function () {
                        $module.removeClass(className.disabled);
                    },

                    disable: function () {
                        $module.addClass(className.disabled);
                    },

                    setState: function (state) {
                        if (module.allows(state)) {
                            $module.addClass(className[state]);
                        }
                    },

                    removeState: function (state) {
                        if (module.allows(state)) {
                            $module.removeClass(className[state]);
                        }
                    },

                    toggle: {
                        state: function () {
                            var
                                apiRequest,
                                requestCancelled
                            ;
                            if (module.allows('active') && module.is.enabled()) {
                                module.refresh();
                                if ($.fn.api !== undefined) {
                                    apiRequest = $module.api('get request');
                                    requestCancelled = $module.api('was cancelled');
                                    if (requestCancelled) {
                                        module.debug('API Request cancelled by beforesend');
                                        settings.activateTest = function () {
                                            return false;
                                        };
                                        settings.deactivateTest = function () {
                                            return false;
                                        };
                                    }
                                    else if (apiRequest) {
                                        module.listenTo(apiRequest);
                                        return;
                                    }
                                }
                                module.change.state();
                            }
                        }
                    },

                    listenTo: function (apiRequest) {
                        module.debug('API request detected, waiting for state signal', apiRequest);
                        if (apiRequest) {
                            if (text.loading) {
                                module.update.text(text.loading);
                            }
                            $.when(apiRequest)
                                .then(function () {
                                    if (apiRequest.state() == 'resolved') {
                                        module.debug('API request succeeded');
                                        settings.activateTest = function () {
                                            return true;
                                        };
                                        settings.deactivateTest = function () {
                                            return true;
                                        };
                                    }
                                    else {
                                        module.debug('API request failed');
                                        settings.activateTest = function () {
                                            return false;
                                        };
                                        settings.deactivateTest = function () {
                                            return false;
                                        };
                                    }
                                    module.change.state();
                                })
                            ;
                        }
                    },

                    // checks whether active/inactive state can be given
                    change: {

                        state: function () {
                            module.debug('Determining state change direction');
                            // inactive to active change
                            if (module.is.inactive()) {
                                module.activate();
                            }
                            else {
                                module.deactivate();
                            }
                            if (settings.sync) {
                                module.sync();
                            }
                            settings.onChange.call(element);
                        },

                        text: function () {
                            if (module.is.textEnabled()) {
                                if (module.is.disabled()) {
                                    module.verbose('Changing text to disabled text', text.hover);
                                    module.update.text(text.disabled);
                                }
                                else if (module.is.active()) {
                                    if (text.hover) {
                                        module.verbose('Changing text to hover text', text.hover);
                                        module.update.text(text.hover);
                                    }
                                    else if (text.deactivate) {
                                        module.verbose('Changing text to deactivating text', text.deactivate);
                                        module.update.text(text.deactivate);
                                    }
                                }
                                else {
                                    if (text.hover) {
                                        module.verbose('Changing text to hover text', text.hover);
                                        module.update.text(text.hover);
                                    }
                                    else if (text.activate) {
                                        module.verbose('Changing text to activating text', text.activate);
                                        module.update.text(text.activate);
                                    }
                                }
                            }
                        }

                    },

                    activate: function () {
                        if (settings.activateTest.call(element)) {
                            module.debug('Setting state to active');
                            $module
                                .addClass(className.active)
                            ;
                            module.update.text(text.active);
                            settings.onActivate.call(element);
                        }
                    },

                    deactivate: function () {
                        if (settings.deactivateTest.call(element)) {
                            module.debug('Setting state to inactive');
                            $module
                                .removeClass(className.active)
                            ;
                            module.update.text(text.inactive);
                            settings.onDeactivate.call(element);
                        }
                    },

                    sync: function () {
                        module.verbose('Syncing other buttons to current state');
                        if (module.is.active()) {
                            $allModules
                                .not($module)
                                .state('activate');
                        }
                        else {
                            $allModules
                                .not($module)
                                .state('deactivate')
                            ;
                        }
                    },

                    get: {
                        text: function () {
                            return (settings.selector.text)
                                ? $module.find(settings.selector.text).text()
                                : $module.html()
                                ;
                        },
                        textFor: function (state) {
                            return text[state] || false;
                        }
                    },

                    flash: {
                        text: function (text, duration, callback) {
                            var
                                previousText = module.get.text()
                            ;
                            module.debug('Flashing text message', text, duration);
                            text = text || settings.text.flash;
                            duration = duration || settings.flashDuration;
                            callback = callback || function () {
                            };
                            module.update.text(text);
                            setTimeout(function () {
                                module.update.text(previousText);
                                callback.call(element);
                            }, duration);
                        }
                    },

                    reset: {
                        // on mouseout sets text to previous value
                        text: function () {
                            var
                                activeText = text.active || $module.data(metadata.storedText),
                                inactiveText = text.inactive || $module.data(metadata.storedText)
                            ;
                            if (module.is.textEnabled()) {
                                if (module.is.active() && activeText) {
                                    module.verbose('Resetting active text', activeText);
                                    module.update.text(activeText);
                                }
                                else if (inactiveText) {
                                    module.verbose('Resetting inactive text', activeText);
                                    module.update.text(inactiveText);
                                }
                            }
                        }
                    },

                    update: {
                        text: function (text) {
                            var
                                currentText = module.get.text()
                            ;
                            if (text && text !== currentText) {
                                module.debug('Updating text', text);
                                if (settings.selector.text) {
                                    $module
                                        .data(metadata.storedText, text)
                                        .find(settings.selector.text)
                                        .text(text)
                                    ;
                                }
                                else {
                                    $module
                                        .data(metadata.storedText, text)
                                        .html(text)
                                    ;
                                }
                            }
                            else {
                                module.debug('Text is already set, ignoring update', text);
                            }
                        }
                    },

                    setting: function (name, value) {
                        module.debug('Changing setting', name, value);
                        if ($.isPlainObject(name)) {
                            $.extend(true, settings, name);
                        }
                        else if (value !== undefined) {
                            if ($.isPlainObject(settings[name])) {
                                $.extend(true, settings[name], value);
                            }
                            else {
                                settings[name] = value;
                            }
                        }
                        else {
                            return settings[name];
                        }
                    },
                    internal: function (name, value) {
                        if ($.isPlainObject(name)) {
                            $.extend(true, module, name);
                        }
                        else if (value !== undefined) {
                            module[name] = value;
                        }
                        else {
                            return module[name];
                        }
                    },
                    debug: function () {
                        if (!settings.silent && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.debug.apply(console, arguments);
                            }
                        }
                    },
                    verbose: function () {
                        if (!settings.silent && settings.verbose && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.verbose.apply(console, arguments);
                            }
                        }
                    },
                    error: function () {
                        if (!settings.silent) {
                            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
                            module.error.apply(console, arguments);
                        }
                    },
                    performance: {
                        log: function (message) {
                            var
                                currentTime,
                                executionTime,
                                previousTime
                            ;
                            if (settings.performance) {
                                currentTime = new Date().getTime();
                                previousTime = time || currentTime;
                                executionTime = currentTime - previousTime;
                                time = currentTime;
                                performance.push({
                                    'Name': message[0],
                                    'Arguments': [].slice.call(message, 1) || '',
                                    'Element': element,
                                    'Execution Time': executionTime
                                });
                            }
                            clearTimeout(module.performance.timer);
                            module.performance.timer = setTimeout(module.performance.display, 500);
                        },
                        display: function () {
                            var
                                title = settings.name + ':',
                                totalTime = 0
                            ;
                            time = false;
                            clearTimeout(module.performance.timer);
                            $.each(performance, function (index, data) {
                                totalTime += data['Execution Time'];
                            });
                            title += ' ' + totalTime + 'ms';
                            if (moduleSelector) {
                                title += ' \'' + moduleSelector + '\'';
                            }
                            if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
                                console.groupCollapsed(title);
                                if (console.table) {
                                    console.table(performance);
                                }
                                else {
                                    $.each(performance, function (index, data) {
                                        console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
                                    });
                                }
                                console.groupEnd();
                            }
                            performance = [];
                        }
                    },
                    invoke: function (query, passedArguments, context) {
                        var
                            object = instance,
                            maxDepth,
                            found,
                            response
                        ;
                        passedArguments = passedArguments || queryArguments;
                        context = element || context;
                        if (typeof query == 'string' && object !== undefined) {
                            query = query.split(/[\. ]/);
                            maxDepth = query.length - 1;
                            $.each(query, function (depth, value) {
                                var camelCaseValue = (depth != maxDepth)
                                    ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                                    : query
                                ;
                                if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) {
                                    object = object[camelCaseValue];
                                }
                                else if (object[camelCaseValue] !== undefined) {
                                    found = object[camelCaseValue];
                                    return false;
                                }
                                else if ($.isPlainObject(object[value]) && (depth != maxDepth)) {
                                    object = object[value];
                                }
                                else if (object[value] !== undefined) {
                                    found = object[value];
                                    return false;
                                }
                                else {
                                    module.error(error.method, query);
                                    return false;
                                }
                            });
                        }
                        if ($.isFunction(found)) {
                            response = found.apply(context, passedArguments);
                        }
                        else if (found !== undefined) {
                            response = found;
                        }
                        if ($.isArray(returnedValue)) {
                            returnedValue.push(response);
                        }
                        else if (returnedValue !== undefined) {
                            returnedValue = [returnedValue, response];
                        }
                        else if (response !== undefined) {
                            returnedValue = response;
                        }
                        return found;
                    }
                };

                if (methodInvoked) {
                    if (instance === undefined) {
                        module.initialize();
                    }
                    module.invoke(query);
                }
                else {
                    if (instance !== undefined) {
                        instance.invoke('destroy');
                    }
                    module.initialize();
                }
            })
        ;

        return (returnedValue !== undefined)
            ? returnedValue
            : this
            ;
    };

    $.fn.state.settings = {

        // module info
        name: 'State',

        // debug output
        debug: false,

        // verbose debug output
        verbose: false,

        // namespace for events
        namespace: 'state',

        // debug data includes performance
        performance: true,

        // callback occurs on state change
        onActivate: function () {
        },
        onDeactivate: function () {
        },
        onChange: function () {
        },

        // state test functions
        activateTest: function () {
            return true;
        },
        deactivateTest: function () {
            return true;
        },

        // whether to automatically map default states
        automatic: true,

        // activate / deactivate changes all elements instantiated at same time
        sync: false,

        // default flash text duration, used for temporarily changing text of an element
        flashDuration: 1000,

        // selector filter
        filter: {
            text: '.loading, .disabled',
            active: '.disabled'
        },

        context: false,

        // error
        error: {
            beforeSend: 'The before send function has cancelled state change',
            method: 'The method you called is not defined.'
        },

        // metadata
        metadata: {
            promise: 'promise',
            storedText: 'stored-text'
        },

        // change class on state
        className: {
            active: 'active',
            disabled: 'disabled',
            error: 'error',
            loading: 'loading',
            success: 'success',
            warning: 'warning'
        },

        selector: {
            // selector for text node
            text: false
        },

        defaults: {
            input: {
                disabled: true,
                loading: true,
                active: true
            },
            button: {
                disabled: true,
                loading: true,
                active: true,
            },
            progress: {
                active: true,
                success: true,
                warning: true,
                error: true
            }
        },

        states: {
            active: true,
            disabled: true,
            error: true,
            loading: true,
            success: true,
            warning: true
        },

        text: {
            disabled: false,
            flash: false,
            hover: false,
            active: false,
            inactive: false,
            activate: false,
            deactivate: false
        }

    };


})(jQuery, window, document);
